package edu.rpi.tw.impav;

import java.awt.Color;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import processing.core.PGraphics;
import traer.physics.Attraction;
import traer.physics.Particle;

import com.hp.hpl.jena.ontology.Individual;
import com.hp.hpl.jena.rdf.model.Property;

/**
 * This class is a customized Java-fied version of the demo at:
 * http://www.cricketschirping.com/processing/GraphLayout2/
 * 
 * @author jim
 * 
 */
public class Node extends Particle implements Comparable<Node> {

    private Individual concept;

    private Set<Attraction> attractions = new HashSet<Attraction>();
    
    public Node(Individual concept, Graph g) {
        super(1.0);
        this.concept = concept;
        Property prefLabel = concept.getModel().getProperty(
                ConceptMap.SKOS + "prefLabel");
        label = concept.getProperty(prefLabel).getString();
        id = concept.getURI();
        this.g = g;
        System.out.println(label);
    }

    public void buildAttractions() {
        for (Node node : g.getNodes()) {
            if (node != this) {
                Attraction magnet = g.ps.makeAttraction(this, node,
                        g.MAGNET_CONSTANT, g.MAGNET_DISTANCE);
                attractions.add(magnet);
                node.attractions.add(magnet);
                Attraction gravity = g.ps.makeAttraction(this, node,
                        g.GRAVITY_CONSTANT, g.GRAVITY_DISTANCE);
                attractions.add(gravity);
                node.attractions.add(gravity);
            }
        }
    }

    public synchronized void remove() {
        for (Attraction a : attractions) {
            g.ps.removeAttraction(a);
        }
        attractions.clear();
        g.ps.removeParticle(this);
        synchronized (g.getNodeMap()) {
            g.getNodeMap().remove(getId());
        }
        synchronized (g.getEdgeMap()) {
            g.getEdgeMap().remove(this);
        }
    }

    String label = "";
    String id;
    int w = 30;
    int h = 30;
    private float baseTextSize = 64;
    float textSize = baseTextSize;
    double textWidth = -1;

    Color c = new Color(0);
    boolean isSelected = false;
    boolean isHovering = false;
    boolean isDragging = false;
    Graph g;
    double lastTouch = 0;
    private List<Tweet> tweets = Collections.synchronizedList(new LinkedList<Tweet>());

    private int documentFrequency = 0;
    
    private boolean visible = true;

    private float tfIdf;

    private float radius;

    private double fade;

    private double highlightFade;
    
    public String getId() {
        return id;
    }

    public boolean containsPoint(float x, float y) {
        double dx = position().x() - x;
        double dy = position().y() - y;

        return (Math.abs(dx) < w * g.getCentroid().z() / 2.0 && Math.abs(dy) < h
                * g.getCentroid().z() / 2.0);
    }

    public void setSize(int r) {
        h = r;
        w = r;
    }

    public int getSize() {
        return w;
    }

    public void setLabel(String label) {
        this.label = label;
    }

    public String getLabel() {
        return label;
    }

    double ageSinceLastTouch() {
        return age() - lastTouch;
    }

    public double getTextWidth() {
        if (textWidth < 0) {
            return (10+baseTextSize);
        }
        return textWidth*(10+baseTextSize*getTfIdf());
    }
    
    public synchronized void update(PGraphics graphics) {
        // TF-IDF
        double tf = (double)tweets.size()/(double)g.getCurrentTweetCount();
        double idf = Math.log((double)g.getTotalTweetCount()/(double)documentFrequency);
        tfIdf =  (float)(tf*idf);

        if (textWidth < 0) {
            graphics.textSize(100);
            textWidth = graphics.textWidth(label)/100f;
        }
        radius = (1.2f*(float)getTextWidth()) / 2;


    }

    public synchronized void draw(PGraphics graphics) {
        if (!isVisible()) return;
        float tfidf = getTfIdf();
        if (this.tweets.size() == 0) return;

        
        long age = new Date().getTime() 
                   - tweets.get(tweets.size()-1).added.getTime();
        fade = Math.pow(2, -(double)age/(double)g.lifetime);
        highlightFade = Math.pow(2, -(double)age/500f);

        // Flash red and then fade to a washed-out gray/blue/purple, 
        // and decay the alpha to 0. 
        graphics.fill((float)(40f*highlightFade + 200), 
                      (float)((150-150*highlightFade)+50), 
                      (float)((200-200*highlightFade)+50), 
                      (float)fade*256f);
        graphics.strokeWeight(1);
        graphics.stroke(0x000000, (float)fade*64f);
        textSize = 10 + baseTextSize * tfidf;
        graphics.textSize(textSize);

        float width = getRadius() * 2;
        graphics.ellipse((float) position().x(), (float) position().y(), width,
                textSize*1.5f);

        graphics.fill(0, 0, 0, (float)fade*256f);
        graphics.text(label,
                      (float)(position().x() - getTextWidth() / 2), 
                      (float) position().y() + (0.4f*textSize));
    }

    public float getTfIdf() {
        return tfIdf;
    }

    public float getRadius() {
        return radius;
    }

    void setSelected(boolean b) {
        isSelected = b;
        lastTouch = age();
    }

    void setHovering(boolean b) {
        isHovering = b;
    }

    void setDragging(boolean b) {
        lastTouch = age();
        isDragging = b;
        if (b) {
            makeFixed();
        } else {
            makeFree();
        }
    }

    public Individual getConcept() {
        return concept;
    }

    public List<Tweet> getTweets() {
        return tweets;
    }

    public synchronized void addTweet(Tweet t) {
        tweets.add(t);
        documentFrequency++;
    }

    public void setVisible(boolean visible) {
        this.visible = visible;
    }

    public boolean isVisible() {
        return visible;
    }

    @Override
    public int compareTo(Node n) {
        return new Float(getTfIdf()).compareTo(n.getTfIdf());
    }

}
